import java.io.*;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.util.*;
import java.text.SimpleDateFormat;
import java.net.ServerSocket;
import java.net.Socket;


public class Git {

    /*主函数*/
    public static void main(String[] args) {

        /*处理未传入参数的情况*/
        if (args.length == 0) {
            System.out.println("请输入指令！\n");
            Help.help(args);
            return;
        }

        /*获取命令*/
        String operation = args[0];

        /*执行命令*/
        switch (operation) {

            case "help" -> Help.help(args);

            case "init" -> {

                Init init = new Init();
                String working_directory = init.find_working_directory();
                if (working_directory == null) {
                    init.init();
                } else {
                    System.out.println("位于工作区内部，当前【工作区】路径：" + working_directory); //位于工作区内部时不可以再初始化了
                }

            }

            case "add" -> Blob.add(args);

            case "commit" -> Commit.commit(args);

            case "status" -> Status.status();

            case "rm" -> Rm.rm(args);

            case "log" -> {
                Log log = new Log();
                log.log();
            }

            case "reflog" -> {
                Log reflog = new Log();
                reflog.reflog();
            }

            case "reset" -> Reset.reset(args);

            case "push" -> Push.push(args);

            case "pull" -> Pull.pull(args);

            case "server" -> GitServer.server(args);

            default -> {
                System.out.println("请输入正确的指令！");
                Help.help(args);
            }
        }

    }

}

class Help {

    /*help*/
    static void help(String[] args) {

        if (args.length != 2) {
            Help help = new Help();
            help.help_default();
        } else {
            System.out.println();
            System.out.println("/***** 使用提示：请输入java Git 【您的指令】 *****/");
            switch (args[1]) {
                case "help" -> {
                    System.out.println("  help：【help】输出使用提示");
                    System.out.println("        【help 指令】输出指定指令的使用提示");
                }
                case "init" -> {
                    System.out.println("  init：【init】初始化工作区，以使用Git的其它命令");
                }
                case "add" -> {
                    System.out.println("   add：【add 文件（夹）路径】可将该文件（夹）添加到暂存区，或从暂存区修改/删除该文件（夹）");
                    System.out.println("        【add .】只使用当前所在文件夹（而非工作区根目录）更新暂存区");
                    System.out.println("        您可以自由选择绝对路径或相对路径，但路径中有空格时，请务必在首尾加上英文双引号（\"）");
                }
                case "commit" -> {
                    System.out.println("commit：【commit 备注】将暂存区内容添加到本地仓库中");
                }
                case "status" -> {
                    System.out.println("status：【status】用于查看在上次提交之后是否有对文件进行再次修改");
                }
                case "rm" -> {
                    System.out.println("    rm：【rm 文件名】在暂存区中删除对应条目，并在工作区中删除该文件（不可用于文件夹）");
                    System.out.println("        【rm --cached 文件名】只在暂存区中删除对应条目，不在工作区中删除该文件（不可用于文件夹）");
                    System.out.println("        【rm -r 文件夹名】在暂存区中删除对应条目，并在工作区中删除该文件夹（不可用于文件）");
                    System.out.println("        【rm -r --cached 文件名】只在暂存区中删除对应条目，不在工作区中删除该文件夹（不可用于文件）");
                    System.out.println("        您可以自由选择绝对路径或相对路径，但路径中有空格时，请务必在首尾加上英文双引号（\"）");
                }
                case "log" -> {
                    System.out.println("   log：【log】查看最近一次以及之前的提交记录");
                }
                case "reflog" -> {
                    System.out.println("reflog：【reflog】查看本地所有的提交记录");
                }
                case "reset" -> {
                    System.out.println(" reset：【reset --soft 提交ID】把最近提交记录修改为指定的版本");
                    System.out.println("        【reset (--mixed) 提交ID】为默认选项，在--soft的基础上，修改暂存区为指定的版本");
                    System.out.println("        【reset --hard 提交ID】在--mixed（默认）的基础上，修改工作区为指定的版本");
                }
                case "push" -> {
                    System.out.println("  push：【push 服务器的IP地址 端口号】推送本地仓库到远程，并恢复远程工作区为本地仓库中最近提交的版本");
                }
                case "pull" -> {
                    System.out.println("  pull：【pull 服务器的IP地址 端口号】调取远程仓库到本地，并恢复本地工作区为远程仓库中最近提交的版本");
                }
                case "server" -> {
                    System.out.println("server：【server 选定的端口号】在服务器使用，与客户端进行对接");
                }
                default -> {
                    Help help = new Help();
                    help.help_default();
                }
            }
            System.out.println("/***** 使用提示：请输入java Git 【您的指令】 *****/");
        }

    }

    /*default*/
    void help_default() {
        System.out.println();
        System.out.println("/***** 使用提示：请输入java Git 【您的指令】 *****/");
        System.out.println("  help：【help】输出使用提示");
        System.out.println("        【help 指令】输出指定指令的使用提示");
        System.out.println("  init：【init】初始化工作区，以使用Git的其它命令");
        System.out.println("   add：【add 文件（夹）路径】可将该文件（夹）添加到暂存区，或从暂存区修改/删除该文件（夹）");
        System.out.println("        【add .】只使用当前所在文件夹（而非工作区根目录）更新暂存区");
        System.out.println("        您可以自由选择绝对路径或相对路径，但路径中有空格时，请务必在首尾加上英文双引号（\"）");
        System.out.println("commit：【commit 备注】将暂存区内容添加到本地仓库中");
        System.out.println("status：【status】用于查看在上次提交之后是否有对文件进行再次修改");
        System.out.println("    rm：【rm 文件名】在暂存区中删除对应条目，并在工作区中删除该文件（不可用于文件夹）");
        System.out.println("        【rm --cached 文件名】只在暂存区中删除对应条目，不在工作区中删除该文件（不可用于文件夹）");
        System.out.println("        【rm -r 文件夹名】在暂存区中删除对应条目，并在工作区中删除该文件夹（不可用于文件）");
        System.out.println("        【rm -r --cached 文件名】只在暂存区中删除对应条目，不在工作区中删除该文件夹（不可用于文件）");
        System.out.println("        您可以自由选择绝对路径或相对路径，但路径中有空格时，请务必在首尾加上英文双引号（\"）");
        System.out.println("   log：【log】查看最近一次以及之前的提交记录");
        System.out.println("reflog：【reflog】查看本地所有的提交记录");
        System.out.println(" reset：【reset --soft 提交ID】把最近提交记录修改为指定的版本");
        System.out.println("        【reset (--mixed) 提交ID】为默认选项，在--soft的基础上，修改暂存区为指定的版本");
        System.out.println("        【reset --hard 提交ID】在--mixed（默认）的基础上，修改工作区为指定的版本");
        System.out.println("  push：【push 服务器的IP地址 端口号】推送本地仓库到远程，并恢复远程工作区为本地仓库中最近提交的版本");
        System.out.println("  pull：【pull 服务器的IP地址 端口号】调取远程仓库到本地，并恢复本地工作区为远程仓库中最近提交的版本");
        System.out.println("server：【server 选定的端口号】在服务器使用，与客户端进行对接");
        System.out.println("/***** 使用提示：请输入java Git 【您的指令】 *****/");
    }

}

class Init {

    /*无参数构造方法*/
    Init() {
    }

    /*初始化*/
    String init() {

        /*获取必要的几个路径*/
        String working_directory = System.getProperty("user.dir"); //工作区的绝对路径
        System.out.println("\n当前路径：" + working_directory);
        String pointgit = working_directory + File.separator + ".git"; //.git文件夹的绝对路径
        String objects = pointgit + File.separator + "objects"; //objects文件夹的绝对路径

        /*创建.git文件夹*/
        if (!new File(pointgit).mkdir()) {
            System.out.println(".git文件夹已存在！");
        } else {
            System.out.println(".git文件夹创建成功！");
        }

        /*创建objects文件夹*/
        if (!new File(objects).mkdir()) {
            System.out.println(".git/objects文件夹已存在！");
        } else {
            System.out.println(".git/objects文件夹创建成功！");
        }

        /*创建index文件*/
        File index_file = new File(pointgit + File.separator + "index");
        try {
            if (!index_file.createNewFile()) {
                System.out.println("index文件已存在！");
            } else {
                System.out.println("index文件创建成功！");
                Index index = new Index();
                index.writeIndex(pointgit, index);
            }
        } catch (IOException e) {
            System.out.println("创建index文件时出现异常！");
        }

        /*创建HEAD文件*/
        File head_file = new File(pointgit + File.separator + "HEAD");
        try {
            if (!head_file.createNewFile()) {
                System.out.println("HEAD文件已存在！");
            } else {
                System.out.println("HEAD文件创建成功！");
            }
        } catch (IOException e) {
            System.out.println("创建HEAD文件时出现异常！");
        }

        /*创建reflog文件*/
        File reflog_file = new File(pointgit + File.separator + "reflog");
        if (reflog_file.exists()) {
            System.out.println("reflog文件已存在！");
        } else {
            try (ObjectOutputStream oos_reflog = new ObjectOutputStream(new FileOutputStream(pointgit + File.separator + "reflog"))) {
                Log reflog = new Log();
                reflog.commit_id = new ArrayList<>();
                oos_reflog.writeObject(reflog);
                oos_reflog.flush();
                System.out.println("reflog文件创建成功！");
            } catch (IOException e) {
                System.out.println("创建reflog文件时出现异常！");
            }
        }

        return working_directory;
    }

    /*找到工作区根目录并返回，找不到返回null*/
    String find_working_directory() {
        String path_temp = System.getProperty("user.dir"); //当前路径
        while (path_temp != null) {
            File pointgit_dir = new File(path_temp + File.separator + ".git"); //创建.git文件夹的File类对象
            if (pointgit_dir.isDirectory()) { //.git存在并且是文件夹（isDirectory：文件不存在时返回false）
                return path_temp;
            } else {
                File dir_temp = new File(path_temp);
                path_temp = dir_temp.getParent();
            }
        }
        return null;
    }

}

class Index implements Serializable {

    TreeMap<String, String> index_TreeMap;

    /*无参数构造方法*/
    Index() {
        index_TreeMap = new TreeMap<>();
    }

    /*打印Index对象中的TreeMap*/
    void print_IndexTreeMap() {
        Set<String> keys = index_TreeMap.keySet();
        for (String key : keys) {
            System.out.println(key + " : " + index_TreeMap.get(key));
        }
    }

    /*读取index文件*/
    Index readIndex(String pointgit) {
        Index index = null;
        try (ObjectInputStream ois_index = new ObjectInputStream(new FileInputStream(pointgit + File.separator + "index"))) {
            index = (Index) ois_index.readObject();
        } catch (IOException | ClassNotFoundException e) {
            System.out.println("读取index文件时出错！");
            System.exit(1); //异常退出
        }
        return index;
    }

    /*覆写index文件*/
    boolean writeIndex(String pointgit, Index index) {
        try (ObjectOutputStream oos_index = new ObjectOutputStream(new FileOutputStream(pointgit + File.separator + "index"))) {
            oos_index.writeObject(index);
            oos_index.flush();
            return true;
        } catch (IOException e) {
            System.out.println("写入index文件时出错！");
            return false;
        }
    }

}

class SHA1 {

    /*计算字节数组的SHA1*/
    public static String bytes2SHA1(byte[] bytes) {
        try {
            MessageDigest md = MessageDigest.getInstance("SHA-1"); //指明SHA-1
            md.update(bytes);
            return bytes2Hex(md.digest()); //返回字符串的SHA-1值
        } catch (Exception ex) {
            System.out.println("计算SHA1时出现异常！");
            System.exit(1);
        }
        return null;
    }

    /*计算字符串的SHA1*/
    public static String str2SHA1(String str) {
        try {
            MessageDigest md = MessageDigest.getInstance("SHA-1"); //指明SHA-1
            md.update(str.getBytes(StandardCharsets.UTF_8)); //"utf-8"
            byte[] bytes = md.digest(); //得到字节数组
            return bytes2Hex(bytes); //返回字符串的SHA-1值
        } catch (Exception ex) {
            System.out.println("计算SHA1时出现异常！");
            System.exit(1);
        }
        return null;
    }

    /*将字节数组转换为字符串形式的十六进制数字字符串*/
    public static String bytes2Hex(byte[] bytes) {
        StringBuilder strb = new StringBuilder();
        for (byte byte_temp : bytes) {
            String hex = Integer.toHexString(byte_temp & 0xFF);
            if (hex.length() < 2) { //处理十六进制数只有1位的情况
                strb.append(0); //要补到2位
            }
            strb.append(hex);
        }
        return strb.toString();
    }

}

class Blob implements Serializable {

    int size; //原文件大小
    byte[] bytes; //文件对应的字节数组
    String sha1; //通过字节数组计算出的 SHA1（用于 blob 文件名）

    /*含参构造方法*/
    Blob(File file) {

        this.size = (int) file.length();
        this.bytes = new byte[size];
        try (FileInputStream fileInputStream = new FileInputStream(file)) {
            if (fileInputStream.read(bytes) != size) {
                System.out.println("读取【" + file.getAbsolutePath() + "】时出现异常！");
                System.exit(1);
            }
        } catch (IOException e) {
            System.out.println("读取【" + file.getAbsolutePath() + "】时出现异常！");
            System.exit(1);
        }
        this.sha1 = SHA1.bytes2SHA1(bytes);

    }

    /*add：处理新增/修改的单个文件*/
    static void add_contribute_modify_file(File file, String file_relative_path, String objects, Index index) {
        Blob blob = new Blob(file);
        String blob_path = objects + File.separator + blob.sha1;
        File blob_file = new File(blob_path);
        if (!blob_file.exists()) {
            try (ObjectOutputStream oos_blob = new ObjectOutputStream(new FileOutputStream(blob_path))) {
                oos_blob.writeObject(blob); // 把Blob类的对象blob写进文件中
            } catch (IOException e) {
                System.out.println("创建【" + file_relative_path + "】的blob文件时出现异常！");
            }
        }
        if (!index.index_TreeMap.containsKey(file_relative_path)) { //如果这个来自工作区的文件不在哈希表中
            index.index_TreeMap.put(file_relative_path, blob.sha1); //添加（contribute）新的一项
        } else {//如果这个来自工作区的文件在哈希表中
            index.index_TreeMap.replace(file_relative_path, blob.sha1); //更新（modify）对应的SHA1，无论SHA1是否变化都覆盖原来的（比较字符串也不省资源）
        }
    } //作为静态方法比较合适

    /*add：处理单个文件夹*/
    static void add_directory(File directory, String directory_relative_path, String directory_absolute_path, boolean is_w, String objects, Index index) {

        /*将此目录在暂存区的记录全部清除*/
        if (!is_w) { //不是工作区根目录
            Set<String> keys_temp = index.index_TreeMap.keySet();
            String[] keys = keys_temp.toArray(new String[0]);
            for (String key : keys) {
                if (key.startsWith(directory_relative_path) && key.substring(directory_relative_path.length()).startsWith(File.separator)) {
                    index.index_TreeMap.remove(key);
                }
            }
        } else { //是工作区根目录
            index.index_TreeMap = new TreeMap<>();
        }

        /*在空文件夹内生成一个空文件，这样可以add空文件夹*/
        File[] files = directory.listFiles();
        assert files != null;
        if (files.length == 0) { //在空文件夹里生成一个空白文件，这样就可以添加空文件夹了
            File file0 = new File(directory_absolute_path + File.separator + ".keep");
            try {
                if (!file0.createNewFile()) {
                    System.out.println("未能成功地在【" + directory_relative_path + "】创建【.keep文件】！");
                }
                files = directory.listFiles();
            } catch (IOException e) {
                System.out.println("未能成功地在【" + directory_relative_path + "】创建【.keep文件】！");
                return;
            }
        }

        /*重新add当前文件夹*/
        assert files != null;
        if (is_w) {
            for (File file : files) {
                String file_relative_path = file.getName();
                if (file.isFile()) {
                    Blob.add_contribute_modify_file(file, file_relative_path, objects, index);
                } else {
                    if (!file.getName().equals(".git")) { //只有工作区根目录才有判断是不是.git文件夹的必要
                        String file_absolute_path = file.getAbsolutePath();
                        Blob.add_directory(file, file_relative_path, file_absolute_path, false, objects, index);
                    }
                }
            }
        } else {
            for (File file : files) {
                String file_relative_path = directory_relative_path + File.separator + file.getName();
                if (file.isFile()) {
                    Blob.add_contribute_modify_file(file, file_relative_path, objects, index);
                } else {
                    String file_absolute_path = file.getAbsolutePath();
                    Blob.add_directory(file, file_relative_path, file_absolute_path, false, objects, index);
                }
            }
        }


    }

    /*add*/
    static void add(String[] args) { //作为静态方法比较合适

        /*判断是否初始化，获取工作区路径*/
        Init init = new Init();
        String working_directory = init.find_working_directory();
        if (working_directory == null) {
            System.out.println("请先执行init操作！");
            return;
        } else {
            System.out.println("当前【工作区】路径：" + working_directory);
        }

        /*判断是否继续输入了必要的信息*/
        if (args.length == 1) {
            System.out.println("请重启并输入.或者文件名！");
            return;
        }

        /*获取其它相关路径*/
        String pointgit = working_directory + File.separator + ".git";
        String objects = pointgit + File.separator + "objects";

        /*从index文件中读取index*/
        Index index = new Index();
        index = index.readIndex(pointgit);

        /*具体操作*/
        if (!args[1].equals(".")) { //参数不是.
            int num = args.length;
            for (int i = 1; i < num; i = i + 1) { //允许一次add多个文件/文件夹

                /*防止用户输入路径时键入了英文双引号*/
                String file_path = args[i];
                if (file_path.startsWith("\"")) {
                    file_path = file_path.substring(1, file_path.length() - 1);
                }

                /*得到格式规范的绝对路径和相对路径（相对于工作区文件夹而非当前文件夹）*/
                File file = new File(file_path);
                String file_absolute_path = file.getAbsolutePath();
                String file_relative_path = "";
                if (!file_absolute_path.startsWith(working_directory)) {
                    System.out.println("【" + file_absolute_path + "】位于工作区外！");
                    continue;
                } else {
                    if (!file_absolute_path.equals(working_directory)) {
                        if (!file_absolute_path.substring(working_directory.length()).startsWith(File.separator)) {
                            System.out.println("【" + file_absolute_path + "】位于工作区外！");
                            continue;
                        } else {
                            file_relative_path = file_absolute_path.substring(working_directory.length()); //得到相对于工作区文件夹的相对路径，最后可能是文件分隔符
                            if (!file_relative_path.startsWith(File.separator)) {
                                System.out.println("【" + file_absolute_path + "】位于工作区外！");
                                continue;
                            } else {
                                file_relative_path = file_relative_path.substring(1);
                            }
                        }
                    }
                }

                /*判断是否为.git文件夹，或者位于.git文件夹内*/
                if (file_relative_path.equals(".git") || file_relative_path.startsWith(".git") && file_relative_path.substring(4).startsWith(File.separator)) {
                    System.out.println("【" + file_relative_path + "】是【.git文件夹】的路径，或者位于【.git文件夹】内！");
                    continue;
                }

                /*正式开始*/
                file = new File(file_absolute_path);

                if (file.exists()) { //文件（夹）在工作区存在

                    if (file.isFile()) {
                        Blob.add_contribute_modify_file(file, file_relative_path, objects, index); //*****新增或修改*****
                    } else {
                        Blob.add_directory(file, file_relative_path, file_absolute_path, file_absolute_path.equals(working_directory), objects, index);
                    }

                } else { //文件（夹）在工作区不存在

                    if (!index.index_TreeMap.containsKey(file_relative_path)) { //有可能作为暂存区内的一个非空文件夹，此时要删除其中的内容
                        boolean in_stage = false;
                        Set<String> set = index.index_TreeMap.keySet();
                        String[] list = set.toArray(new String[0]);
                        for (String key : list) {
                            if (key.startsWith(file_relative_path) && key.substring(file_relative_path.length()).startsWith(File.separator)) {
                                index.index_TreeMap.remove(key);
                                in_stage = true;
                            }
                        }
                        if (!in_stage) {
                            System.out.println("【" + file_relative_path + "】既不在工作区，也不在暂存区！");//输出相对路径较为简洁
                        }
                    } else {
                        index.index_TreeMap.remove(file_relative_path);//*****删除*****（此时就是暂存区内单纯的一个文件）
                    }
                }

            }
        } else { //参数是.
            String directory_absolute_path = System.getProperty("user.dir");
            String directory_relative_path = "";
            if (!directory_absolute_path.equals(working_directory)) {
                directory_relative_path = directory_absolute_path.substring(working_directory.length() + 1);
            }
            if (directory_relative_path.equals(".git") || directory_relative_path.startsWith(".git") && directory_relative_path.substring(4).startsWith(File.separator)) {
                System.out.println("【" + directory_relative_path + "】是【.git文件夹】的路径，或者位于【.git文件夹】内！");
                return;
            }
            File directory = new File(directory_absolute_path);
            Blob.add_directory(directory, directory_relative_path, directory_absolute_path, directory_absolute_path.equals(working_directory), objects, index);
        }

        /*把index写回index文件*/
        index.writeIndex(pointgit, index);
        System.out.println("\n/***** 当前暂存区 *****/");
        index.print_IndexTreeMap();

    }

}

class Tree implements Serializable {

    int size; //用于计算 SHA1 的字符串对应字节数组的长度
    String sha1; //利用 tree_TreeMap 存储的键生成的 SHA1
    TreeMap<String, String> tree_TreeMap; //被固化到 tree 文件中的暂存区快照

    /*含参数构造方法，得到Index对象对应的tree文件*/
    Tree(Index index) {
        this.tree_TreeMap = index.index_TreeMap; // tree对象内容与index一致

        ArrayList<String> name_hash_list = new ArrayList<>();
        Set<String> keys = index.index_TreeMap.keySet(); //键的集合
        for (String key : keys) {
            name_hash_list.add(key + " : " + index.index_TreeMap.get(key) + "\n");
        }
        name_hash_list.sort(Comparator.naturalOrder()); //按照文件名排序

        StringBuilder strb = new StringBuilder();
        name_hash_list.forEach(strb::append);
        String sha1_temp = strb.toString();// 用于计算SHA1的值
        byte[] bytes = sha1_temp.getBytes(StandardCharsets.UTF_8);
        this.size = bytes.length;
        this.sha1 = SHA1.bytes2SHA1(bytes);

        System.out.println("***** tree_sha1 *****\n" + sha1 + "\n");
    }

}

class Commit implements Serializable {

    String pre_commit_id = "";//如果不赋初值并进行处理，第一次commit时会出现错误；也不适合用null，因为输出相关信息时会出错。
    String tree_id; //对应tree文件的文件名
    String message; //用户输入的提交信息
    long commit_time; //提交时间的时间戳
    String sha1; //同时也是cur_commit_id

    /*含参构造方法*/
    Commit(Index index, String pointgit, String message) {

        this.commit_time = System.currentTimeMillis();
        this.message = message;
        Tree tree = new Tree(index); //固定当前的暂存区到tree中
        this.tree_id = tree.sha1;

        /*生成tree文件*/
        try (ObjectOutputStream oos_tree = new ObjectOutputStream(new FileOutputStream(pointgit + File.separator + "objects" + File.separator + tree.sha1))) {
            oos_tree.writeObject(tree); // 把tree写进文件中
        } catch (IOException e) {
            System.out.println("生成tree文件时出现异常！");
            System.exit(1);
        }

        /*打开HEAD文件，并得到上一次的commit id（SHA1）*/
        File head_file = new File(pointgit + File.separator + "HEAD");
        StringBuilder strb = new StringBuilder();
        try (FileReader fr = new FileReader(head_file)) {
            int k = 0;
            while (k != -1) {
                k = fr.read();
                strb.append((char) k);
            }
            this.pre_commit_id = strb.toString();
            pre_commit_id = pre_commit_id.substring(0, pre_commit_id.length() - 1); //要剔除最后多余的那个符号
        } catch (IOException e) {
            System.out.println("读取上一次commit的ID时出现异常！");
            System.exit(1);
        }

        /*计算本次的commit id（SHA1）*/
        String sha1_temp = this.tree_id + "\n" + this.message + "\n" + this.commit_time + "\n" + this.pre_commit_id;
        this.sha1 = SHA1.str2SHA1(sha1_temp);

        /*输出相关信息*/
        System.out.println("***** commit_content *****\n" + sha1_temp + "\n");
        System.out.println("***** commit_sha1 *****\n" + this.sha1);

    }

    /*输出commit文件中保存的信息*/
    void print_commit() {
        String time = new SimpleDateFormat("yyyy-MM-dd HH-mm-ss").format(commit_time);
        System.out.println("commit ID: " + sha1);
        System.out.println("pre commit ID: " + pre_commit_id); //便于在reflog中寻找自己需要的提交记录
        System.out.println("commit message: " + message);
        System.out.println("commit time: " + time);
    }

    /*commit指令调用的方法*/
    static void commit(String[] args) {

        /*判断是否初始化，获取工作区路径*/
        Init init = new Init();
        String working_directory = init.find_working_directory();
        if (working_directory == null) {
            System.out.println("请先执行init操作！");
            return;
        } else {
            System.out.println("当前【工作区】路径：" + working_directory);
        }

        /*判断是否继续输入了必要的信息，并获取message*/
        if (args.length != 3 || !args[1].equals("-m")) {
            System.out.println("请输入【java Git commit -m 您的提交信息】！");
            return;
        }
        String message = args[2];

        /*获取其它相关路径*/
        String pointgit = working_directory + File.separator + ".git";
        String objects = pointgit + File.separator + "objects";

        /*从index文件中读取index*/
        Index index = new Index();
        index = index.readIndex(pointgit);

        /*生成commit对象和文件*/
        Commit commit = new Commit(index, pointgit, message);
        try (ObjectOutputStream oos_commit = new ObjectOutputStream(new FileOutputStream(pointgit + File.separator + "objects" + File.separator + commit.sha1))) {
            oos_commit.writeObject(commit);
        } catch (IOException e) {
            System.out.println("生成commit文件时出现异常！");
            System.exit(1);
        }

        /*写入reflog文件*/
        Log reflog = null;
        try (ObjectInputStream ois_reflog = new ObjectInputStream(new FileInputStream(pointgit + File.separator + "reflog"))) {
            reflog = (Log) ois_reflog.readObject();
        } catch (IOException | ClassNotFoundException e) {
            System.out.println("读取reflog文件时出错！");
        }
        if (reflog != null) {
            reflog.commit_id.add(0, commit.sha1); //按照从新到旧的顺序
        }
        try (ObjectOutputStream oos_reflog = new ObjectOutputStream(new FileOutputStream(pointgit + File.separator + "reflog"))) {
            oos_reflog.writeObject(reflog);
        } catch (IOException e) {
            System.out.println("写入reflog文件时出错！");
        }

        /*更新HEAD文件，写入新的commit ID*/
        try (PrintStream stream = new PrintStream(pointgit + File.separator + "HEAD")) {
            stream.print(commit.sha1);//写入的字符串
        } catch (IOException e) {
            System.out.println("更新HEAD文件时出现异常！");
        }

        /*输出本次和上次commit的文件变动*/
        Commit.changes(commit, objects);

    }

    /*输出本次和上次commit的文件变动*/
    static void changes(Commit commit, String objects) {

        /*打开本次commit对应的Tree文件*/
        Tree tree;
        try (ObjectInputStream ois_tree = new ObjectInputStream(new FileInputStream(objects + File.separator + commit.tree_id))) {
            tree = (Tree) ois_tree.readObject();
        } catch (IOException | ClassNotFoundException e) {
            System.out.println("打开本次提交对应的tree文件时出现异常！");
            return;
        }

        /*输出变动*/
        if (commit.pre_commit_id.length() > 2) { //说明不是第一次commit

            /*打开上一次的commit文件*/
            Commit pre_commit;
            try (ObjectInputStream ois_pre_commit = new ObjectInputStream(new FileInputStream(objects + File.separator + commit.pre_commit_id))) {
                pre_commit = (Commit) ois_pre_commit.readObject();
            } catch (IOException | ClassNotFoundException e) {
                System.out.println("读取上次的commit ID时出现异常！");
                return;
            }

            /*打开上一次commit对应的Tree文件*/
            Tree pre_tree;
            try (ObjectInputStream ois_pre_tree = new ObjectInputStream(new FileInputStream(objects + File.separator + pre_commit.tree_id))) {
                pre_tree = (Tree) ois_pre_tree.readObject();
            } catch (IOException | ClassNotFoundException e) {
                System.out.println("打开上次提交对应的tree文件时出现异常！");
                return;
            }

            /*此时有tree和pre_tree两个Tree类型的对象，要比较二者哈希表的区别*/
            Set<String> cur_TreeMap_keys = tree.tree_TreeMap.keySet(); // 本次键的集合
            Set<String> pre_TreeMap_keys = pre_tree.tree_TreeMap.keySet(); // 上次键的集合
            ArrayList<String> contributed = new ArrayList<>();
            ArrayList<String> modified = new ArrayList<>();
            for (String cur_path : cur_TreeMap_keys) {
                if (!pre_TreeMap_keys.contains(cur_path)) { // 上次没有该文件，但这次有了
                    contributed.add(cur_path); // 放进contributed
                } else { // 上次有该文件，这次还有
                    if (!(tree.tree_TreeMap.get(cur_path)).equals(pre_tree.tree_TreeMap.get(cur_path))) { // SHA1变了
                        modified.add(cur_path);
                    }
                    pre_TreeMap_keys.remove(cur_path); // 从上次的集合中移除该文件名，以减少后续工作量。因为上一次的tree不用写进文件，所以对应哈希表变了也无所谓
                }
            }
            ArrayList<String> deleted = new ArrayList<>(pre_TreeMap_keys); //剩下的全放进deleted

            /*开始输出*/
            System.out.println("\n***** 相比较上次提交发生变化的文件 *****");
            for (String contributed_path : contributed) {
                System.out.println("(+)" + contributed_path);
            }
            for (String modified_path : modified) {
                System.out.println("(M)" + modified_path);
            }
            for (String deleted_path : deleted) {
                System.out.println("(-)" + deleted_path);
            }
            System.out.println();

        } else { //说明是第一次commit
            System.out.println("\n***** 首次提交的文件 *****");
            Set<String> cur_TreeMap_keys = tree.tree_TreeMap.keySet(); // 本次键的集合
            for (String contributed_path : cur_TreeMap_keys) {
                System.out.println("(+)" + contributed_path);
            }
            System.out.println();
        }
    }

}

class Status {

    /*status*/
    static void status() {

        /*判断是否初始化，获取工作区路径*/
        Init init = new Init();
        String working_directory = init.find_working_directory();
        if (working_directory == null) {
            System.out.println("请先执行init操作！");
            return;
        } else {
            System.out.println("当前【工作区】路径：" + working_directory);
        }
        String pointgit = working_directory + File.separator + ".git";

        /*打开HEAD文件*/
        File head_file = new File(pointgit + File.separator + "HEAD");

        /*得到HEAD中存放的的commit id（SHA1）*/
        String commit_id = null;
        StringBuilder strb = new StringBuilder();
        try (FileReader fr = new FileReader(head_file)) {
            int k = 0;
            while (k != -1) {
                k = fr.read();
                strb.append((char) k);
            }
            commit_id = strb.toString();
            commit_id = commit_id.substring(0, commit_id.length() - 1); //要剔除最后的'\0'
        } catch (IOException e) {
            System.out.println("读取HEAD文件时发生异常！");
            System.exit(1);
        }

        /*得到上次被提交暂存区的文件名数组*/
        TreeMap<String, String> p_TreeMap = new TreeMap<>();
        String[] p_list = new String[0];

        if (commit_id.length() > 2) {

            /*读取commit文件*/
            Commit commit = null;
            try (ObjectInputStream ois_commit = new ObjectInputStream(new FileInputStream(pointgit + File.separator + "objects" + File.separator + commit_id))) {
                commit = (Commit) ois_commit.readObject();
            } catch (IOException | ClassNotFoundException e) {
                System.out.println("读取【" + commit_id + "】对应的commit文件时发生异常！");
                System.exit(1);
            }

            /*读取tree文件，得到上次提交的暂存区*/
            Tree tree = null;
            try (ObjectInputStream ois_tree = new ObjectInputStream(new FileInputStream(pointgit + File.separator + "objects" + File.separator + commit.tree_id))) {
                tree = (Tree) ois_tree.readObject();
            } catch (IOException | ClassNotFoundException e) {
                System.out.println("读取【" + commit.tree_id + "】对应的tree文件时发生异常！");
                System.exit(1);
            }
            p_TreeMap = tree.tree_TreeMap;
            Set<String> p_set = p_TreeMap.keySet();
            p_list = p_set.toArray(new String[0]);
        }

        /*读取index*/
        Index index = new Index();
        index = index.readIndex(pointgit);
        TreeMap<String, String> i_TreeMap = index.index_TreeMap;
        Set<String> i_set = i_TreeMap.keySet();
        String[] i_list = i_set.toArray(new String[0]);

        /*获取当前工作区的TreeMap*/
        File w = new File(working_directory);
        TreeMap<String, String> w_TreeMap = new TreeMap<>();
        Status.directory2TreeMap(w, working_directory, w_TreeMap, true);

        /*Changes to be committed*/
        ArrayList<String> c1_c = new ArrayList<>(); //Changes to be committed: (+)
        ArrayList<String> c1_d = new ArrayList<>(); //Changes to be committed: (-)
        ArrayList<String> c1_m = new ArrayList<>(); //Changes to be committed: (M)

        /*Changes not staged for commit & Untracked files*/
        //ArrayList<String> c2_c = new ArrayList<>(); //Untracked files:
        ArrayList<String> c2_d = new ArrayList<>(); //Changes not staged for commit
        ArrayList<String> c2_m = new ArrayList<>(); //Changes not staged for commit

        /*（c1_d）被删除且被提交了的文件：工作区和暂存区都没有，但是tree有（被删除且被add）*/
        for (String p : p_list) {
            if (!i_TreeMap.containsKey(p) && !w_TreeMap.containsKey(p)) {
                c1_d.add(p);
                p_TreeMap.remove(p);
            }
        }

        for (String i : i_list) {

            /*暂存区和工作区都有*/
            if (w_TreeMap.containsKey(i)) {

                /*且SHA1一样*/
                if (w_TreeMap.get(i).equals(i_TreeMap.get(i))) {

                    /*（c1_c）tree没有（被新建且被add）*/
                    if (!p_TreeMap.containsKey(i)) c1_c.add(i);

                    /*（c1_m）tree有且SHA1不一样（被修改且被add）*/
                    if (p_TreeMap.containsKey(i) && !p_TreeMap.get(i).equals(i_TreeMap.get(i))) c1_m.add(i);

                }

                /*（c2_m）且SHA1不同（被修改但还没有add）*/
                else c2_m.add(i);

                w_TreeMap.remove(i);

            }
            /*（c2_d）暂存区有，但是工作区没有（被删除但还没有add）*/
            else c2_d.add(i);

            i_TreeMap.remove(i);
        }

        /*（c2_c）工作区有，但是暂存区没有（被新建但还没有add）*/
        Set<String> c2_c = w_TreeMap.keySet();

        System.out.println("\n");

        System.out.println("Changes to be committed:");
        System.out.println("     created:");
        for (String c : c1_c) {
            System.out.println("          " + c);
        }
        System.out.println("     deleted:");
        for (String c : c1_d) {
            System.out.println("          " + c);
        }
        System.out.println("    modified:");
        for (String c : c1_m) {
            System.out.println("          " + c);
        }

        System.out.println();

        System.out.println("Changes not staged for commit:");
        System.out.println("     deleted:");
        for (String c : c2_d) {
            System.out.println("          " + c);
        }
        System.out.println("    modified:");
        for (String c : c2_m) {
            System.out.println("          " + c);
        }

        System.out.println();

        System.out.println("Untracked files:");
        System.out.println("     created:");
        for (String c : c2_c) {
            System.out.println("          " + c);
        }

        System.out.println();
    }

    /*获取某个文件夹对应的TreeMap*/
    static void directory2TreeMap(File directory, String working_directory, TreeMap<String, String> w_TreeMap, boolean is_w) {
        File[] files = directory.listFiles();
        assert files != null;
        if (is_w) {
            for (File file : files) {
                if (file.isFile()) {
                    String file_absolute_path = file.getAbsolutePath();
                    String file_relative_path = file_absolute_path.substring(working_directory.length() + 1);
                    Blob blob = new Blob(file);
                    w_TreeMap.put(file_relative_path, blob.sha1);
                } else {
                    if (!file.getName().equals(".git")) { //只有工作区根目录才有判断的必要
                        Status.directory2TreeMap(file, working_directory, w_TreeMap, false);
                    }
                }
            }
        } else {
            for (File file : files) {
                if (file.isFile()) {
                    String file_absolute_path = file.getAbsolutePath();
                    String file_relative_path = file_absolute_path.substring(working_directory.length() + 1);
                    Blob blob = new Blob(file);
                    w_TreeMap.put(file_relative_path, blob.sha1);
                } else {
                    Status.directory2TreeMap(file, working_directory, w_TreeMap, false); //不用判断是否为【.git】文件夹
                }
            }
        }
    }

}

class Rm {

    /*rm*/
    static void rm(String[] args) {

        /*判断是否初始化，获取工作区路径*/
        Init init = new Init();
        String working_directory = init.find_working_directory();
        if (working_directory == null) {
            System.out.println("请先执行init操作！");
            return;
        } else {
            System.out.println("当前【工作区】路径：" + working_directory);
        }

        /*判断用户是否只输入了rm*/
        if (args.length == 1) {
            System.out.println("请重启并输入【java Git rm (--cached) 文件名（以空格分隔开）】，或者【java Git rm -r (--cached) 文件夹名（以空格分隔开）】");
            return;
        }

        /*从index文件中读取index*/
        String pointgit = working_directory + File.separator + ".git";
        Index index = new Index();
        index = index.readIndex(pointgit);

        /*输出执行删除命令前的暂存区*/
        System.out.println("\n***** 执行删除命令前的暂存区 *****");
        index.print_IndexTreeMap();
        System.out.println();

        /*防止用户的输入带有双引号*/
        for (int i = 0; i < args.length; i = i + 1) {
            if (args[i].startsWith("\"")) {
                args[i] = args[i].substring(1, args[i].length() - 1);
            }
        }

        /*执行rm命令*/
        if (args.length == 2) {
            if (args[1].equals("--cached") || args[1].equals("-r")) {
                System.out.println("请重启并输入【java Git rm (--cached) 文件名（以空格分隔开，不可以是文件夹）】，或者【java Git rm -r (--cached) 文件夹名（以空格分隔开，不可以是文件）】");
                return;
            } else {
                Rm.rm_file(args[1], working_directory, false, index);
            }
        } else if (args.length == 3) {
            if (args[1].equals("-r")) { //删除文件夹
                if (args[2].equals("--cached")) {
                    System.out.println("请重启并输入【java Git rm (--cached) 文件名（以空格分隔开，不可以是文件夹）】，或者【java Git rm -r (--cached) 文件夹名（以空格分隔开，不可以是文件）】");
                    return;
                } else {
                    Rm rm = new Rm();
                    rm.rm_directory(args[2], working_directory, false, index);
                }
            } else { //删除文件
                if (args[1].equals("--cached")) {
                    Rm.rm_file(args[2], working_directory, true, index);
                } else {
                    Rm.rm_file(args[1], working_directory, false, index);
                    Rm.rm_file(args[2], working_directory, false, index);
                }
            }
        } else {
            if (args[1].equals("-r")) {
                Rm rm = new Rm();
                if (args[2].equals("--cached")) {
                    for (int i = 3; i < args.length; i++) {
                        rm.rm_directory(args[i], working_directory, true, index);
                    }
                } else {
                    for (int i = 2; i < args.length; i++) {
                        rm.rm_directory(args[i], working_directory, false, index);
                    }
                }
            } else if (args[1].equals("--cached")) {
                for (int i = 2; i < args.length; i++) {
                    Rm.rm_file(args[i], working_directory, true, index);
                }
            } else {
                for (int i = 1; i < args.length; i++) {
                    Rm.rm_file(args[i], working_directory, false, index);
                }
            }
        }

        /*打印rm后的暂存区，并把index写回index文件*/
        System.out.println("\n***** 执行删除命令后的暂存区 *****");
        index.print_IndexTreeMap();
        System.out.println();
        index.writeIndex(pointgit, index);

    }

    /*处理单个文件，cached决定是否要从工作区删除*/
    static void rm_file(String file_path, String working_directory, boolean cached, Index index) {

        File file = new File(file_path);

        /*处理相对路径和绝对路径*/
        String file_relative_path;
        String file_absolute_path = file.getAbsolutePath();
        if (file_absolute_path.startsWith(working_directory + File.separator)) {
            file_relative_path = file_absolute_path.substring(working_directory.length() + 1);
        } else {
            System.out.println("【" + file_path + "】不在工作区内！"); //只有输入绝对路径时才会出现这种情况
            return;
        }

        /*在.git文件夹内*/
        if (file_relative_path.startsWith(".git" + File.separator)) {
            System.out.println("【" + file_relative_path + "】位于.git文件夹内！");
        }

        /*存在且为文件夹*/
        if (file.isDirectory()) {
            System.out.println("【" + file_relative_path + "】是文件夹！");
            return;
        }

        /*从暂存区内删除，通过返回值看是否存在*/
        if (index.index_TreeMap.remove(file_relative_path) != null) {
            System.out.println("已从暂存区删除【" + file_relative_path + "】");
        } else {
            System.out.println("暂存区内不存在【" + file_relative_path + "】");
        }

        /*用户要求删除工作区内对应的文件*/
        if (!cached) {
            if (file.delete()) {
                System.out.println("已从工作区删除【" + file_relative_path + "】");
            } else {
                System.out.println("【" + file_relative_path + "】不存在，或者删除失败！");
            }
        }

    }

    /*处理单个文件夹，cached决定是否要从工作区删除*/
    void rm_directory(String directory_path, String working_directory, boolean cached, Index index) {

        File directory = new File(directory_path);

        /*处理相对路径和绝对路径*/
        String directory_relative_path;
        String directory_absolute_path = directory.getAbsolutePath();
        if (directory_absolute_path.startsWith(working_directory + File.separator)) {
            directory_relative_path = directory_absolute_path.substring(working_directory.length() + 1);
        } else {
            System.out.println("【" + directory_path + "】不在工作区内部！"); //只有输入绝对路径时才会出现这种情况
            return;
        }

        /*在.git文件夹内，或者就是.git文件夹*/
        if (directory_relative_path.startsWith(".git" + File.separator) || directory_relative_path.equals(".git")) {
            System.out.println("【" + directory_relative_path + "】位于.git文件夹内，或者就是.git文件夹！");
        }

        /*存在且为文件*/
        if (directory.isFile()) {
            System.out.println("【" + directory_relative_path + "】是文件！");
            return;
        }

        /*从暂存区内删除*/
        if (cached) {
            Set<String> keys_temp = index.index_TreeMap.keySet();
            String[] keys = keys_temp.toArray(new String[0]);
            boolean in_stage = false;
            for (String key : keys) {
                if (key.startsWith(directory_relative_path + File.separator)) {
                    index.index_TreeMap.remove(key);
                    System.out.println("已从暂存区删除【" + key + "】");
                    in_stage = true;
                }
            }
            if (!in_stage) {
                System.out.println("暂存区内不存在【" + directory_relative_path + "】");
            } else {
                System.out.println("已从暂存区删除【" + directory_relative_path + "】");
            }
        }

        /*用户要求删除工作区内对应的文件夹*/
        if (!cached) {

            if (!directory.exists()) {

                /*从暂存区删除相关文件*/
                Set<String> keys_temp = index.index_TreeMap.keySet();
                String[] keys = keys_temp.toArray(new String[0]);
                boolean in_stage = false;
                for (String key : keys) {
                    if (key.startsWith(directory_relative_path + File.separator)) {
                        index.index_TreeMap.remove(key);
                        System.out.println("已从暂存区删除【" + key + "】");
                        in_stage = true;
                    }
                }
                if (!in_stage) {
                    System.out.println("暂存区内不存在【" + directory_relative_path + "】");
                } else {
                    System.out.println("已从暂存区删除【" + directory_relative_path + "】");
                }

                System.out.println("工作区内不存在【" + directory_relative_path + "】");
                return;

            }//如此可以确保文件夹存在于工作区内
            Rm rm = new Rm();
            rm.delete_directory(directory_relative_path, directory, working_directory, index);

        }

    }

    /*从工作区删除文件夹*/
    void delete_directory(String directory_relative_path, File directory, String working_directory, Index index) {
        File[] files = directory.listFiles();
        assert files != null;
        boolean in_stage = false;
        for (File file : files) {
            String file_absolute_path = file.getAbsolutePath();
            String file_relative_path = file_absolute_path.substring(working_directory.length() + 1);
            if (file.isFile()) {
                if (index.index_TreeMap.remove(file_relative_path) == null) {
                    System.out.println("暂存区内不存在【" + file_relative_path + "】");
                } else {
                    System.out.println("已从暂存区删除【" + file_relative_path + "】");
                    in_stage = true;
                }
                if (file.delete()) {
                    System.out.println("已从工作区删除【" + file_relative_path + "】");
                } else {
                    System.out.println("【" + file_relative_path + "】删除失败");
                }
            } else {
                Rm rm = new Rm();
                rm.delete_directory(file_relative_path, file, working_directory, index);
            }
        }

        /*输出已从暂存区删除文件夹，或者暂存区不存在文件夹*/
        if (in_stage) {
            System.out.println("已从暂存区删除【" + directory_relative_path + "】");
        } else {
            System.out.println("暂存区内不存在【" + directory_relative_path + "】");
        }

        /*文件夹已清空，最后从工作区中删除空文件夹*/
        if (directory.delete()) {
            System.out.println("已从工作区删除【" + directory_relative_path + "】");
        } else {
            System.out.println("【" + directory_relative_path + "】删除失败");
        }


    }

}

class Log implements Serializable {

    ArrayList<String> commit_id; //本地所有提交记录

    /*无参数构造方法*/
    Log() {
    }

    /*log命令*/
    void log() {

        /*判断是否初始化，获取工作区路径*/
        Init init = new Init();
        String working_directory = init.find_working_directory();
        if (working_directory == null) {
            System.out.println("请先执行init操作！");
            return;
        } else {
            System.out.println("当前【工作区】路径：" + working_directory);
        }
        String pointgit = working_directory + File.separator + ".git";

        System.out.println("\n");

        /*打开HEAD文件*/
        File head_file = new File(pointgit + File.separator + "HEAD");

        /*得到HEAD中存放的的commit id（SHA1）*/
        String commit_id = "";
        StringBuilder strb = new StringBuilder();
        try (FileReader fr = new FileReader(head_file)) {
            int k = 0;
            while (k != -1) {
                k = fr.read();
                strb.append((char) k);
            }
            commit_id = strb.toString();
            commit_id = commit_id.substring(0, commit_id.length() - 1); //要剔除最后的'\0'
        } catch (IOException e) {
            System.out.println("读取HEAD文件时发生异常！");
        }

        while (!commit_id.equals("")) {

            /*读取commit文件*/
            Commit commit;
            try (ObjectInputStream ois_commit = new ObjectInputStream(new FileInputStream(pointgit + File.separator + "objects" + File.separator + commit_id))) {
                commit = (Commit) ois_commit.readObject();
            } catch (IOException | ClassNotFoundException e) {
                System.out.println("读取【" + commit_id + "】对应的commit文件时发生异常！");
                return;
            }

            /*类似单链表*/
            commit.print_commit();
            commit_id = commit.pre_commit_id;
            System.out.println();
        }

    }

    /*reflog命令*/
    void reflog() {

        /*判断是否初始化，获取工作区路径*/
        Init init = new Init();
        String working_directory = init.find_working_directory();
        if (working_directory == null) {
            System.out.println("请先执行init操作！");
            return;
        } else {
            System.out.println("当前【工作区】路径：" + working_directory);
        }
        String pointgit = working_directory + File.separator + ".git";

        System.out.println("\n");

        /*读取reflog文件*/
        Log reflog = null;
        try (ObjectInputStream ois_reflog = new ObjectInputStream(new FileInputStream(pointgit + File.separator + "reflog"))) {
            reflog = (Log) ois_reflog.readObject();
        } catch (IOException | ClassNotFoundException e) {
            System.out.println("读取reflog文件时出错！");
            System.exit(1); //异常退出
        }

        /*打印所有的commit记录*/
        for (String cid : reflog.commit_id) {
            /*读取commit文件*/
            Commit commit;
            try (ObjectInputStream ois_commit = new ObjectInputStream(new FileInputStream(pointgit + File.separator + "objects" + File.separator + cid))) {
                commit = (Commit) ois_commit.readObject();
            } catch (IOException | ClassNotFoundException e) {
                System.out.println("读取【" + commit_id + "】对应的commit文件时发生异常！\n");
                break;
            }
            commit.print_commit();
            System.out.println();
        }

    }

}

class Reset {

    /*reset*/
    static void reset(String[] args) {

        /*判断是否初始化，获取工作区路径*/
        Init init = new Init();
        String working_directory = init.find_working_directory();
        if (working_directory == null) {
            System.out.println("请先执行init操作！");
            return;
        } else {
            System.out.println("当前【工作区】路径：" + working_directory);
        }
        String pointgit = working_directory + File.separator + ".git";

        if (args.length == 1) {
            System.out.println("请重启并输入reset的模式或者commit ID！");
            return;
        }

        String mode = args[1];
        switch (mode) {
            case "--soft" -> {
                if (args.length != 3) {
                    System.out.println("您的输入有误！");
                } else {
                    Reset reset_soft = new Reset();
                    reset_soft.soft(pointgit, args[2]);
                }
            }

            case "--mixed" -> {
                if (args.length != 3) {
                    System.out.println("您的输入有误！");
                } else {
                    Reset reset_mixed = new Reset();
                    reset_mixed.mixed(pointgit, args[2]);
                }
            }

            case "--hard" -> {
                if (args.length != 3) {
                    System.out.println("您的输入有误！");
                } else {
                    Scanner scanner = new Scanner(System.in);
                    System.out.print("警告：此操作会导致工作区未备份的文件丢失，如要继续请输入y，否则请输入其它任意内容！\n>");
                    String str = scanner.nextLine();
                    scanner.close();
                    System.out.println();
                    if (!str.equals("y")) {
                        System.out.println("未执行reset命令！");
                        return;
                    }
                    Reset reset_hard = new Reset();
                    reset_hard.hard(working_directory, pointgit, args[2]);
                }
            }

            default -> {
                if (args.length != 2) {
                    System.out.println("您的输入有误！");
                } else {
                    Reset reset_default = new Reset();
                    reset_default.mixed(pointgit, args[1]);
                }
            }
        }
    }

    /*soft*/
    Commit soft(String pointgit, String commit_id) {

        /*得到commit对象，并判断它是否真的为Commit类，以免把tree或者blob覆盖进了HEAD，同时为mixed做准备*/
        Commit commit;
        try (ObjectInputStream ois_commit = new ObjectInputStream(new FileInputStream(pointgit + File.separator + "objects" + File.separator + commit_id))) {
            commit = (Commit) ois_commit.readObject();
        } catch (IOException | ClassNotFoundException | ClassCastException e) {
            System.out.println("请检查您输入的ID是否对应commit文件，或者commit文件是否正常！");
            return null;
        }

        /*把commit ID写入HEAD*/
        try (PrintStream stream = new PrintStream(pointgit + File.separator + "HEAD")) {
            stream.print(commit_id);
        } catch (FileNotFoundException e) {
            System.out.println("写入HEAD文件时出现异常！");
            return null; //未成功写入文件，后续最好不要再继续执行了，所以返回null
        }
        return commit;
    }

    /*mixed/default*/
    TreeMap<String, String> mixed(String pointgit, String commit_id) {

        /*先执行soft，并得到Commit对象*/
        Reset reset_soft = new Reset();
        Commit commit = reset_soft.soft(pointgit, commit_id);
        if (commit == null) {
            return null;
        }

        /*得到tree对象*/
        String tree_id = commit.tree_id;
        Tree tree;
        try (ObjectInputStream ois_tree = new ObjectInputStream(new FileInputStream(pointgit + File.separator + "objects" + File.separator + tree_id))) {
            tree = (Tree) ois_tree.readObject();
        } catch (IOException | ClassNotFoundException e) {
            System.out.println("读取tree文件时出现异常！");
            return null;
        }

        /*修改index文件*/
        Index index = new Index();
        index.index_TreeMap = tree.tree_TreeMap;
        if (index.writeIndex(pointgit, index)) {
            System.out.println("***** 当前的暂存区 *****");
            index.print_IndexTreeMap();
            return index.index_TreeMap;
        } else {
            return null;
        }

    }

    /*hard*/
    void hard(String working_directory, String pointgit, String commit_id) {

        /*先执行mixed，并得到TreeMap对象*/
        Reset reset_mixed = new Reset();
        TreeMap<String, String> index_TreeMap = reset_mixed.mixed(pointgit, commit_id);

        if (index_TreeMap != null) {

            /*先清空工作区（不删除.git文件夹）*/
            File working_directory_files = new File(working_directory);
            File[] files = working_directory_files.listFiles();
            assert files != null; //在执行之前一定执行过init，所以files一定不为空
            for (File file : files) {
                if (file.isFile()) {
                    if (!file.delete()) {
                        System.out.println("【" + file.getName() + "】删除失败！");
                    }
                } else {
                    String directory_relative_path = file.getName();
                    if (!directory_relative_path.equals(".git")) {
                        Reset reset_delete_directory = new Reset();
                        reset_delete_directory.delete_directory(directory_relative_path, file);
                    }
                }
            }

            /*把blob文件恢复到工作区*/
            Set<String> keys = index_TreeMap.keySet();
            for (String key : keys) {

                /*读取blob文件*/
                Blob blob;
                String blob_sha1 = index_TreeMap.get(key);
                try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(pointgit + File.separator + "objects" + File.separator + blob_sha1))) {
                    blob = (Blob) ois.readObject();
                } catch (IOException | ClassNotFoundException e) {
                    System.out.println("在读取【" + key + "】对应的blob文件【" + blob_sha1 + "】时出现异常！");
                    return;//最好直接终止程序，方便用户立刻处理
                }

                /*创建文件夹*/
                File file = new File(working_directory + File.separator + key);
                File file_p = file.getParentFile();
                if (!file_p.exists()) {
                    if (!file_p.mkdirs()) {
                        System.out.println("在恢复【" + key + "】文件时出现异常！");
                        return;
                    }
                }

                /*把blob中的bytes重新写入文件*/
                try (FileOutputStream fos = new FileOutputStream(file)) {
                    fos.write(blob.bytes);
                    fos.flush();
                } catch (IOException e) {
                    System.out.println("在恢复【" + key + "】文件时出现异常！");
                    return;
                }

            }

        } else {
            System.out.println("未执行【reset --hard " + commit_id + "】！");
        }

    }

    /*从工作区中删除文件夹*/
    void delete_directory(String directory_relative_path, File directory) {
        File[] files = directory.listFiles();
        assert files != null;
        for (File file : files) {
            if (file.isFile()) {
                if (!file.delete()) {
                    System.out.println("【" + directory_relative_path + File.separator + file.getName() + "】删除失败");
                }
            } else {
                Reset reset_delete_directory = new Reset();
                reset_delete_directory.delete_directory(directory_relative_path + File.separator + file.getName(), file);
            }
        }

        /*文件夹已清空，最后从工作区中删除空文件夹*/
        if (!directory.delete()) {
            System.out.println("【" + directory_relative_path + "】删除失败，可能是因为您使用命令行打开了此文件夹！");
        }

    }

}

class Push {

    DataOutputStream dos;
    DataInputStream dis;
    String pointgit;
    String objects;

    /*含参构造方法*/
    Push(DataOutputStream dos, DataInputStream dis, String pointgit, String objects) {
        this.dos = dos;
        this.dis = dis;
        this.pointgit = pointgit;
        this.objects = objects;
    }

    /*push指令*/
    static void push(String[] args) {

        /*检验输入*/
        if (args.length != 3) {
            System.out.println("请重启并输入【java Git push 服务器的IP地址 服务器监听的端口号】！");
            System.out.println("例如【java Git push 156.53.29.231 8686】！");
            return;
        }
        System.out.println();

        /*判断是否初始化和提交，获取工作区路径*/
        Init init = new Init();
        String working_directory = init.find_working_directory();
        if (working_directory == null) {
            System.out.println("请先执行init操作！");
            return;
        } else {
            System.out.println("当前【工作区】路径：" + working_directory);
        }
        String pointgit = working_directory + File.separator + ".git";
        File head_file = new File(pointgit + File.separator + "HEAD");
        if (head_file.length() == 0) {
            System.out.println("请先执行commit操作！");
            return;
        }
        String objects = pointgit + File.separator + "objects";
        System.out.println();

        /*连接*/
        System.out.println("准备连接的套接字为【" + args[1] + "/" + args[2] + "】");
        Socket cs;
        try {
            cs = new Socket(args[1], Integer.parseInt(args[2])); //ClientSocket
            System.out.println("连接成功！\n");
        } catch (IOException e) {
            System.out.println("建立连接时出现异常，请检查您输入的套接字是否正确，或者服务器状态是否正常！");
            return;
        } catch (NumberFormatException e) {
            System.out.println("请检查您输入的端口号是否正确！");
            return;
        }

        /*生成输出流*/
        OutputStream os;
        DataOutputStream dos;
        try {
            os = cs.getOutputStream();
            dos = new DataOutputStream(os);
        } catch (IOException e) {
            System.out.println("创建输出流时出现异常！");
            return;
        }

        /*传输命令*/
        try {
            dos.writeBoolean(true); //告诉服务器是push还是pull，true代表客户端push，false代表客户端pull
            dos.flush();
        } catch (IOException e) {
            System.out.println("传输命令时出现异常！");
            return;
        }

        /*生成输入流*/
        InputStream is;
        DataInputStream dis;
        try {
            is = cs.getInputStream();
            dis = new DataInputStream(is);
        } catch (IOException e) {
            System.out.println("创建输入流时出现异常！");
            return;
        }

        /*正式开始push*/
        Push push = new Push(dos, dis, pointgit, objects);
        push.push_temp();

        /*释放资源*/
        try {
            dos.close();
            os.close();
            dis.close();
            is.close();
            cs.close();
        } catch (IOException e) {
            System.out.println("释放资源时出现异常！");
        }

    }

    /*传输单个文件*/
    void upload(File file) {
        FileInputStream fis = null;
        try {
            /*传输文件的大小*/
            long file_length = file.length();
            dos.writeLong(file_length);
            dos.flush();

            /*传输文件名*/
            dos.writeUTF(file.getName());
            dos.flush();

            /*开始传输文件*/
            fis = new FileInputStream(file);
            byte[] bytes = new byte[1024];
            int length;
            long progress = 0;
            while ((length = fis.read(bytes, 0, bytes.length)) != -1) {
                dos.write(bytes, 0, length);
                dos.flush();
                progress = progress + length;
                System.out.print("【" + file.getName() + "】传输进度：" + (100 * progress / file_length) + "%\r");
            }

            System.out.println();
        } catch (Exception e) {
            System.out.println("传输文件时出现异常！");
        } finally {
            try {
                if (fis != null) {
                    fis.close();
                }
            } catch (IOException e) {
                System.out.println("关闭文件输入流时出现异常！");
            }
        }
    }

    /*正式传输过程，方便客户端和服务器各自调用*/
    void push_temp() {
        System.out.println();
        System.out.println("======== 开始传输文件 ========");

        /*打开objects文件夹*/
        File objects_directory = new File(objects);
        File[] files = objects_directory.listFiles();
        assert files != null;

        /*传输objects文件夹下文件个数*/
        try {
            dos.writeLong(files.length);
            dos.flush();
        } catch (IOException e) {
            System.out.println("传输objects内文件个数时出现异常！");
        }

        /*传输objects文件夹下的文件*/
        for (File f : files) {
            upload(f);
            try {
                boolean status = dis.readBoolean();
                if (!status) {
                    System.out.println("传输【" + f.getName() + "】时出现异常！");
                }
            } catch (IOException e) {
                System.out.println("确认状态时出现异常！");
            }
        }

        /*传输index*/
        File index_file = new File(pointgit + File.separator + "index");
        upload(index_file);
        try {
            boolean status = dis.readBoolean();
            if (!status) {
                System.out.println("传输【index】时出现异常！");
            }
        } catch (IOException e) {
            System.out.println("确认状态时出现异常！");
        }

        /*传输HEAD*/
        File HEAD_file = new File((pointgit + File.separator + "HEAD"));
        upload(HEAD_file);
        try {
            boolean status = dis.readBoolean();
            if (!status) {
                System.out.println("传输【HEAD】时出现异常！");
            }
        } catch (IOException e) {
            System.out.println("确认状态时出现异常！");
        }

        System.out.println("======== 文件传输完毕 ========");
        System.out.println();
    }

}

class Pull {

    DataOutputStream dos;
    DataInputStream dis;
    String pointgit;
    String objects;

    /*含参构造方法*/
    Pull(DataOutputStream dos, DataInputStream dis, String pointgit, String objects) {
        this.dos = dos;
        this.dis = dis;
        this.pointgit = pointgit;
        this.objects = objects;
    }

    /*pull指令*/
    static void pull(String[] args) {

        /*检验输入*/
        if (args.length != 3) {
            System.out.println("请重启并输入【java Git pull 服务器的IP地址 服务器监听的端口号】！");
            System.out.println("例如【java Git pull 156.53.29.231 8686】！");
            return;
        }
        System.out.println();

        /*判断是否初始化，获取工作区路径*/
        Init init = new Init();
        String working_directory = init.find_working_directory();
        String pointgit;
        String objects;
        if (working_directory == null) {
            Scanner scanner = new Scanner(System.in);
            System.out.print("警告：此操作会导致当前文件夹下未备份的文件丢失，如要继续请输入y，否则请输入其它任意内容！\n>");
            String str = scanner.nextLine();
            scanner.close();
            System.out.println();
            if (!str.equals("y")) {
                System.out.println("未执行pull命令！");
                return;
            }
            working_directory = init.init(); //初始化，执行init操作
            pointgit = working_directory + File.separator + ".git";
            objects = pointgit + File.separator + "objects";
        } else {
            pointgit = working_directory + File.separator + ".git";
            objects = pointgit + File.separator + "objects";
            System.out.println("当前【工作区】路径：" + working_directory);
        }
        System.out.println();

        /*连接*/
        System.out.println("准备连接的套接字为【" + args[1] + "/" + args[2] + "】");
        Socket cs;
        try {
            cs = new Socket(args[1], Integer.parseInt(args[2])); //ClientSocket
            System.out.println("连接成功！\n");
        } catch (IOException e) {
            System.out.println("建立连接时出现异常，请检查您输入的套接字是否正确，或者服务器状态是否正常！");
            return;
        } catch (NumberFormatException e) {
            System.out.println("请检查您输入的端口号是否正确！");
            return;
        }

        /*生成输出流*/
        OutputStream os;
        DataOutputStream dos;
        try {
            os = cs.getOutputStream();
            dos = new DataOutputStream(os);
        } catch (IOException e) {
            System.out.println("创建输出流时出现异常！");
            return;
        }

        /*传输命令*/
        try {
            dos.writeBoolean(false); //告诉服务器是push还是pull，true代表push，false代表pull
            dos.flush();
        } catch (IOException e) {
            System.out.println("传输命令时出现异常！");
            return;
        }

        /*生成输入流*/
        InputStream is;
        DataInputStream dis;
        try {
            is = cs.getInputStream();
            dis = new DataInputStream(is);
        } catch (IOException e) {
            System.out.println("创建输入流时出现异常！");
            return;
        }

        /*确认服务器端是可以push == 确认客户端是否可以pull*/
        boolean client_can_pull = false;
        try {
            client_can_pull = dis.readBoolean();
        } catch (IOException e) {
            System.out.println("确认是否可以传输时出现异常！");
            System.exit(1);
        }
        if (!client_can_pull) {
            System.out.println("服务器端从未初始化或提交过！");
            System.exit(0);
        }

        /*正式开始pull*/
        Pull pull = new Pull(dos, dis, pointgit, objects);
        pull.pull_temp();

        /*释放资源*/
        try {
            dos.close();
            os.close();
            dis.close();
            is.close();
            cs.close();
        } catch (IOException e) {
            System.out.println("释放资源时出现异常！");
        }

        /*读取commit ID，执行git reset --hard*/
        File head_file = new File(pointgit + File.separator + "HEAD");
        if (head_file.length() == 0) {
            System.out.println("本机从未执行过commit！");
            return;
        }
        String commit_id = "";
        StringBuilder strb = new StringBuilder();
        try (FileReader fr = new FileReader(head_file)) {
            int k = 0;
            while (k != -1) {
                k = fr.read();
                strb.append((char) k);
            }
            commit_id = strb.toString();
            commit_id = commit_id.substring(0, commit_id.length() - 1); //要剔除最后的'\0'
        } catch (IOException e) {
            System.out.println("读取commit ID时出现异常！");
            System.exit(1);
        }
        Reset reset_hard = new Reset();
        reset_hard.hard(working_directory, pointgit, commit_id);
    }

    /*传输单个文件*/
    boolean download(String objects) {
        boolean status = false;
        FileOutputStream fos = null;
        try {
            /*接收文件大小*/
            long file_length = dis.readLong();

            /*接收文件名*/
            String file_name = dis.readUTF();
            File file = new File(objects + File.separator + file_name);

            /*开始接收文件*/
            fos = new FileOutputStream(file);
            byte[] bytes = new byte[1024];
            int length;
            long progress = 0;
            while (progress < file_length && (length = dis.read(bytes, 0, bytes.length)) != -1) { //判断条件不可颠倒
                fos.write(bytes, 0, length);
                fos.flush();
                progress = progress + length;
                System.out.print("【" + file_name + "】传输进度：" + (100 * progress / file_length) + "%\r");
            }

            System.out.println();
            if (file.length() != file_length) {
                System.out.println("接收文件的过程中出现错误！");
            } else {
                status = true;
            }
        } catch (IOException e) {
            System.out.println("接收文件时出现异常！");
        } finally {
            try {
                if (fos != null) {
                    fos.close();
                }
            } catch (IOException e) {
                System.out.println("关闭文件输出流时出现异常！");
            }
        }

        return status;
    }

    /*正式接收过程，方便客户端和服务器各自调用*/
    void pull_temp() {
        System.out.println();
        System.out.println("======== 开始接收文件 ========");

        /*接收objects文件夹下文件个数*/
        long num;
        try {
            num = dis.readLong();
            //System.out.println("文件个数：" + num);
        } catch (Exception e) {
            System.out.println("接收objects内文件个数时出现异常！");
            return;
        }
        if (num < -1) return;

        /*接收objects文件夹下的文件*/
        for (int i = 1; i <= num; i++) {
            boolean status = download(objects);
            try {
                dos.writeBoolean(status);
                dos.flush();
            } catch (IOException e) {
                System.out.println("确认状态时出现异常！");
            }
        }

        boolean status;

        /*接收index*/
        status = download(pointgit);
        try {
            dos.writeBoolean(status);
            dos.flush();
        } catch (IOException e) {
            System.out.println("确认状态时出现异常！");
        }

        /*接收head*/
        status = download(pointgit);
        try {
            dos.writeBoolean(status);
            dos.flush();
        } catch (IOException e) {
            System.out.println("确认状态时出现异常！");
        }

        System.out.println("======== 文件传输完毕 ========");
        System.out.println();
    }

}

class GitServer {

    /*server*/
    static void server(String[] args) {

        if (args.length != 2) {
            System.out.println("请重启并输入【java Git server 您选择的端口号】！");
            System.out.println("例如【java Git server 8686】！");
            return;
        }

        /*启动服务器，设置端口号*/
        ServerSocket ss;
        try {
            ss = new ServerSocket(Integer.parseInt(args[1])); //ServerSocket
        } catch (IOException e) {
            System.out.println("启动服务器时出现异常，请检查您输入的端口号是否已被占用！");
            return;
        } catch (NumberFormatException e) {
            System.out.println("启动服务器时出现异常，请检查您输入的端口号是否正确！");
            return;
        }

        while (true) {

            /*获取本机IP地址*/
            String ip = "";
            InetAddress localHost;
            try {
                localHost = InetAddress.getLocalHost();
                ip = localHost.getHostAddress();
            } catch (UnknownHostException e) {
                System.out.println("获取本机IP地址时出现异常，请尝试通过其它方法查看本机IP地址！");
            }

            /*打印通信套接字，接收客户端的连接请求*/
            System.out.println("\n等待连接的套接字为【" + ip + "/" + args[1] + "】");
            Socket cs;
            try {
                cs = ss.accept(); //ClientSocket
                System.out.println("连接成功！\n");
            } catch (IOException e) {
                System.out.println("建立连接时出现异常！\n");
                continue;
            }

            /*创建输入流*/
            InputStream is;
            DataInputStream dis;
            try {
                is = cs.getInputStream();
                dis = new DataInputStream(is);
            } catch (IOException e) {
                System.out.println("创建输入流时出现异常！");
                continue;
            }

            /*传输命令*/
            boolean client_is_push;
            try {
                client_is_push = dis.readBoolean(); //从客户端读取是push还是pull，true代表push，false代表pull
            } catch (IOException e) {
                System.out.println("接收命令时出现异常！");
                continue;
            }

            /*生成输出流*/
            OutputStream os;
            DataOutputStream dos;
            try {
                os = cs.getOutputStream();
                dos = new DataOutputStream(os);
            } catch (IOException e) {
                System.out.println("创建输出流时出现异常！");
                continue;
            }

            /*寻找工作区根目录*/
            Init init = new Init();
            String working_directory = init.find_working_directory();

            if (client_is_push) {

                /*判断是否初始化，获取相关路径*/
                String pointgit;
                String objects;
                if (working_directory == null) {
                    working_directory = init.init();
                }
                pointgit = working_directory + File.separator + ".git";
                objects = pointgit + File.separator + "objects";
                System.out.println("当前【工作区】路径：" + working_directory);

                /*正式开始pull*/
                Pull pull = new Pull(dos, dis, pointgit, objects);
                pull.pull_temp();

                /*释放资源*/
                try {
                    dos.close();
                    os.close();
                    dis.close();
                    is.close();
                    cs.close();
                    //ss.close();
                } catch (IOException e) {
                    System.out.println("释放资源时出现异常！");
                }

                /*读取commit ID，执行git reset --hard*/
                File head_file = new File(pointgit + File.separator + "HEAD");
                if (head_file.length() == 0) {
                    System.out.println("本机从未执行过commit！");
                    return;
                }
                String commit_id;
                StringBuilder strb = new StringBuilder();
                try (FileReader fr = new FileReader(head_file)) {
                    int k = 0;
                    while (k != -1) {
                        k = fr.read();
                        strb.append((char) k);
                    }
                    commit_id = strb.toString();
                    commit_id = commit_id.substring(0, commit_id.length() - 1); //要剔除最后的'\0'
                } catch (IOException e) {
                    System.out.println("读取commit ID时出错！");
                    continue;
                }
                Reset reset_hard = new Reset();
                reset_hard.hard(working_directory, pointgit, commit_id);

            } else {

                /*判断是否初始化，获取相关路径*/
                String pointgit;
                String objects;
                if (working_directory == null) {
                    try {
                        dos.writeBoolean(false); //服务器不可以push
                        dos.flush();
                    } catch (IOException e) {
                        System.out.println("确认是否可以传输时出现异常！");
                    }
                    System.out.println("从未初始化过！");
                    continue;
                } else {
                    pointgit = working_directory + File.separator + ".git";
                    File head_file = new File(pointgit + File.separator + "HEAD");
                    if (head_file.length() == 0) {
                        try {
                            dos.writeBoolean(false);
                            dos.flush();
                        } catch (IOException e) {
                            System.out.println("确认是否可以传输时出现异常！");
                            continue;
                        }
                        System.out.println("从未提交过！");
                        continue;
                    }
                    objects = pointgit + File.separator + "objects";
                    System.out.println("当前【工作区】路径：" + working_directory);
                }

                /*客户端可以执行pull == 服务器端可以push*/
                try {
                    dos.writeBoolean(true);
                    dos.flush();
                } catch (IOException e) {
                    System.out.println("确认是否可以传输时出现异常！");
                    continue;
                }

                /*正式开始push*/
                Push push = new Push(dos, dis, pointgit, objects);
                push.push_temp();

                /*释放资源*/
                try {
                    dos.close();
                    os.close();
                    dis.close();
                    is.close();
                    cs.close();
                    //ss.close();
                } catch (IOException e) {
                    System.out.println("释放资源时出现异常！");
                }
            }


        }
    }

}



//2023-01-12 23:18